//#define HybridCLR
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.Networking;


public partial class HotfixLoader
{

    public Assembly HotfixAssembly { get; private set; }
    public const string dllsFolder = "dlls";
    public const string configFile = "manifest.json";
    public float Progress
    {
        get
        {
            return _progress;
        }
        set
        {
            _progress = value;
            if (OnProgress != null)
            {
                OnProgress.Invoke();
            }
        }
    }
    public Action OnProgress;
    private float _progress;
    private string remoteUrl;
    private bool _hasPersistentData = true;
    public HotfixLoader(string remoteUrl = null)
    {
        this.remoteUrl = remoteUrl;
#if PLATFORM_WEBGL || UNITY_WEBGL
        _hasPersistentData = false;
#endif
    }

    public IEnumerator Load(string mainAssembly, bool isEditor)
    {
        Progress = 0f;
        if (isEditor)
        {
            getDefaultAssembly(mainAssembly);
            Progress = 1f;
            yield break;
        }
#if HybridCLR
        yield return Request();
#else
        getDefaultAssembly(mainAssembly);
#endif
        Progress = 1f;
    }
    private void getDefaultAssembly(string AssemblyName)
    {
        var list = System.AppDomain.CurrentDomain.GetAssemblies();
        foreach (var item in list)
        {
            if (item.GetName().Name == AssemblyName)
            {
                HotfixAssembly = item;
                break;
            }
        }
    }

#if HybridCLR
    /// <summary>
    /// 热更
    /// </summary>
    private IEnumerator Request()
    {
        HotfixInfo builtinConfig = null;
        HotfixInfo localConfig = null;
        HotfixInfo remoteConfig = null;
        HotfixInfo finalConfig = null;
        string path = string.Empty;
        var remoteConfigContent = string.Empty;
        UnityWebRequest unityWebRequest = null;

        //加载本地配置
        if (_hasPersistentData)
        {
            path = Path.Combine(Application.persistentDataPath, dllsFolder, configFile);
            Debug.Log(path);
            if (File.Exists(path))
            {
                try
                {
                    localConfig = JsonUtility.FromJson<HotfixInfo>(File.ReadAllText(path));
                    Debug.Log("本地配置文件");
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }
            }
        }
        //加载内置配置
        path = Path.Combine(Application.streamingAssetsPath, dllsFolder, configFile);
#if UNITY_WEBGL
        path += "?" + UnityEngine.Random.Range(100000, 9999999) + "_" + (DateTime.Now.Ticks);
#endif
        unityWebRequest = UnityWebRequest.Get((new System.Uri(path)).AbsoluteUri);
        Debug.Log("内置路径:" + (new System.Uri(path)).AbsoluteUri);
        yield return unityWebRequest.SendWebRequest();
        if (unityWebRequest.result == UnityWebRequest.Result.Success)
        {
            try
            {
                builtinConfig = JsonUtility.FromJson<HotfixInfo>(unityWebRequest.downloadHandler.text);
                Debug.Log("内置配置文件");
            }
            catch (System.Exception e)
            {
                Debug.LogException(e);
            }
        }
        unityWebRequest.Dispose();

        this.Progress = 0.1f;
        //加载远程配置
        if (!string.IsNullOrEmpty(remoteUrl))
        {
            path = Path.Combine(remoteUrl, dllsFolder, configFile);
#if UNITY_WEBGL
            path += "?" + UnityEngine.Random.Range(100000, 9999999) + "_" + (DateTime.Now.Ticks);
#endif
            int retryCount = 3;
            while (retryCount-- > 0)
            {
                unityWebRequest = UnityWebRequest.Get(path);
                yield return unityWebRequest.SendWebRequest();
                if (unityWebRequest.result == UnityWebRequest.Result.Success)
                {
                    try
                    {
                        remoteConfigContent = unityWebRequest.downloadHandler.text;
                        remoteConfig = JsonUtility.FromJson<HotfixInfo>(remoteConfigContent);
                        Debug.Log("远程配置文件");
                        retryCount = -1;
                    }
                    catch (Exception e)
                    {
                        Debug.LogException(e);
                    }
                }
            }
        }
        this.Progress = 0.2f;
        Dictionary<string, DllFileInfo> files = new Dictionary<string, DllFileInfo>();
        string buildtime = null;
        if (builtinConfig != null)
        {
            files.Clear();
            buildtime = builtinConfig.BuildTime;
            //如果有内置配置
            foreach (var item in builtinConfig.MetadataFiles)
            {
                files.Add(item.FileName, item.Clone());
            }
            foreach (var item in builtinConfig.HotfixFiles)
            {
                files.Add(item.FileName, item.Clone());
            }
            finalConfig = builtinConfig;
        }
        if (localConfig != null)
        {
            //如果有本地配置
            files.Clear();
            buildtime = localConfig.BuildTime;
            foreach (var item in localConfig.MetadataFiles)
            {
                files.Add(item.FileName, item.Clone());
            }
            foreach (var item in localConfig.HotfixFiles)
            {
                files.Add(item.FileName, item.Clone());
            }
            finalConfig = localConfig;
        }
        string downloadingFormat = "{0}.downloading";
        var updateList = new List<DllFileInfo>();
        if (remoteConfig != null)
        {
            finalConfig = remoteConfig;
            if (remoteConfig.BuildTime != buildtime && _hasPersistentData)
            {
                foreach (var item in remoteConfig.MetadataFiles)
                {
                    //aot一定要放到内置包里 防止出错
                    continue;
                }
                foreach (var item in remoteConfig.HotfixFiles)
                {
                    if (files.TryGetValue(item.FileName, out var dllFileInfo))
                    {
                        if (dllFileInfo.Version != item.Version)
                        {
                            updateList.Add(item.Clone());
                        }
                    }
                    else
                    {
                        updateList.Add(item.Clone());
                    }
                }
                try
                {
                    path = Path.Combine(Application.persistentDataPath, dllsFolder, configFile);
                    Debug.LogError(path);
                    if (File.Exists(path))
                    {
                        File.Delete(path);
                    }
                    var finfo = new FileInfo(path);
                    if (finfo.Directory != null && !finfo.Directory.Exists)
                    {
                        Directory.CreateDirectory(finfo.FullName);
                    }
                    File.WriteAllText(path, remoteConfigContent);

                    foreach (var item in updateList)
                    {
                        path = Path.Combine(Application.persistentDataPath, dllsFolder, item.FileName);
                        if (File.Exists(path))
                        {
                            File.Delete(path);
                        }
                        path = string.Format(downloadingFormat, path);
                        if (!File.Exists(path))
                        {
                            var fileInfo = new FileInfo(path);
                            var dirInfo = fileInfo.Directory;
                            if (dirInfo != null && !dirInfo.Exists)
                            {
                                Directory.CreateDirectory(dirInfo.FullName);
                            }
                            File.WriteAllText(path, "");
                        }
                    }

                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }
            }
        }
        if (finalConfig == null)
        {
            yield break;
        }

        updateList.Clear();
        foreach (var item in finalConfig.MetadataFiles)
        {
            item.IsAOT = true;
            if (_hasPersistentData)
            {
                path = string.Format(downloadingFormat, Path.Combine(Application.persistentDataPath, dllsFolder, item.FileName));
                if (File.Exists(path))
                {
                    updateList.Add(item);
                }
            }
            else
            {
                updateList.Add(item);
            }
        }
        foreach (var item in finalConfig.HotfixFiles)
        {
            item.IsEntry = item.FileName == finalConfig.MainAssembly;
            if (_hasPersistentData)
            {
                path = string.Format(downloadingFormat, Path.Combine(Application.persistentDataPath, dllsFolder, item.FileName));
                if (File.Exists(path))
                {
                    updateList.Add(item);
                }
            }
            else
            {
                updateList.Add(item);
            }

        }
        if (updateList.Count > 0)
        {
            float baseProgress = this.Progress;
            float totalSize = 0f;
            float loaded = 0f;
            foreach (var item in updateList)
            {
                totalSize += item.Size;
            }
            Debug.LogFormat("更新文件数量{0}", updateList.Count);
            var len = updateList.Count;
            for (int i = 0; i < len; i++)
            {
                var item = updateList[i];
                Debug.LogFormat("开始下载文件 {0}", item.FileName);
                var src = Path.Combine(remoteUrl, dllsFolder, item.FileName + "?" + item.Version);
                int retryCount = 3;
                while (retryCount-- > 0)
                {
                    unityWebRequest = UnityWebRequest.Get(src);
                    yield return unityWebRequest.SendWebRequest();
                    if (unityWebRequest.result != UnityWebRequest.Result.Success)
                    {
                        Debug.LogFormat("下载失败 {0} 重试次数 {1}", item.FileName, 3 - retryCount);
                        continue;
                    }
                    var bytes = unityWebRequest.downloadHandler.data;
                    unityWebRequest.Dispose();
                    if (bytes.Length != item.Size)
                    {
                        Debug.LogFormat("文件大小出错 {0} 配制文件:{1} 下载文件:{2}", item.FileName, item.Size, bytes.Length);
                        continue;
                    }
                    retryCount = -1;
                    if (_hasPersistentData)
                    {
                        var dst = Path.Combine(Application.persistentDataPath, dllsFolder, item.FileName);
                        try
                        {
                            if (File.Exists(dst))
                            {
                                File.Delete(dst);
                            }
                            File.WriteAllBytes(dst, bytes);
                            path = string.Format(downloadingFormat, Path.Combine(Application.persistentDataPath, dllsFolder, item.FileName));
                            if (File.Exists(path))
                            {
                                File.Delete(path);

                            }
                            Debug.LogFormat("下载完成 {0}", item.FileName);
                        }
                        catch (Exception e)
                        {
                            Debug.LogException(e);
                        }
                    }
                    if (item.IsAOT)
                    {
                        var err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(bytes, HybridCLR.HomologousImageMode.SuperSet);
                        item.Loaded = true;
                        Debug.Log($"LoadMetadataForAOTAssembly:{item.FileName}. ret:{err}");
                    }
                    else if (!item.IsEntry)
                    {
                        Assembly.Load(bytes);
                        item.Loaded = true;
                    }
                    else
                    {
                        item.Data = bytes;
                    }

                    loaded += item.Size;
                    this.Progress = baseProgress + (loaded / totalSize) * 0.5f;
                }
            }
        }
        yield return load(finalConfig);

    }
    IEnumerator load(HotfixInfo info)
    {
        //加载程序集
        string path = string.Empty;
        float baseProgress = this.Progress;
        int totalCount = info.MetadataFiles.Length + info.HotfixFiles.Length;
        int loaded = 0;
        foreach (var item in info.MetadataFiles)
        {
            if (item.Loaded)
            {
                loaded++;
                continue;
            }
            yield return loadFile(item);
            if (item.Data != null)
            {
                var err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(item.Data, HybridCLR.HomologousImageMode.SuperSet);
                Debug.Log($"LoadMetadataForAOTAssembly:{item.FileName}. ret:{err}");
                loaded += 1;
                this.Progress = baseProgress + ((float)loaded / (float)totalCount) * 0.2f;
            }
            else
            {
                Debug.LogErrorFormat("load file fail {0}", item);
            }

        }
        //加载热更列表
        var len = info.HotfixFiles.Length;
        while (len-- > 0)
        {
            var item = info.HotfixFiles[len];
            if (string.IsNullOrEmpty(item.FileName)) continue;
            Debug.LogFormat("load hotfix file {0}", item.FileName);
            if (item.Loaded)
            {
                loaded++;
                continue;
            }
            yield return loadFile(item);
            if (item.Data != null)
            {
                var ass = Assembly.Load(item.Data);
                if (item.IsEntry)
                {
                    HotfixAssembly = ass;
                    Debug.LogFormat("find entry persistent {0}", item.FileName);
                }
                loaded += 1;
                this.Progress = baseProgress + ((float)loaded / (float)totalCount) * 0.2f;
            }
            else
            {
                Debug.LogErrorFormat("load file fail {0}", item);
            }
        }
        foreach (var item in info.MetadataFiles)
        {
            item.Data = null;
        }
        foreach (var item in info.HotfixFiles)
        {
            item.Data = null;
        }
    }
    IEnumerator loadFile(DllFileInfo item)
    {
        string path = string.Empty;
        if (item.Data == null)
        {
            if (_hasPersistentData)
            {
                path = Path.Combine(Application.persistentDataPath, dllsFolder, item.FileName);
                if (File.Exists(path))
                {
                    item.Data = File.ReadAllBytes(path);
                }
            }
        }
        if (item.Data == null)
        {
            path = Path.Combine(Application.streamingAssetsPath, dllsFolder, item.FileName);
            if (!_hasPersistentData)
            {
                path += "?" + item.Version;
            }
            var unityWebRequest = UnityWebRequest.Get((new System.Uri(path)).AbsoluteUri);
            yield return unityWebRequest.SendWebRequest();
            if (unityWebRequest.result == UnityWebRequest.Result.Success)
            {
                item.Data = unityWebRequest.downloadHandler.data;
            }
            else
            {
                Debug.LogFormat("load file fail {0}", item);
            }
            unityWebRequest.Dispose();
        }
    }

#endif
}
#if HybridCLR
[System.Serializable]
public class DllFileInfo
{
    public string FileName;
    public int Size;
    public string Version;
    public bool Loaded { get; set; }
    public bool IsAOT { get; set; }
    public bool IsEntry { get; set; }
    public byte[] Data { get; set; }
    public DllFileInfo Clone()
    {
        return new DllFileInfo() { FileName = this.FileName, Size = this.Size, Version = this.Version };
    }
}
public class HotfixInfo
{
    public string BuildTime;
    public DllFileInfo[] MetadataFiles;
    public DllFileInfo[] HotfixFiles;
    public string MainAssembly;
}
#endif